From: Without Boats Date: Wed, 30 Aug 2017 05:55:31 +0000 (-0700) Subject: Alternate registries. X-Git-Tag: archive/raspbian/0.35.0-2+rpi1~3^2^2^2^2^2^2^2~22^2~5^2~30^2~13 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/success//%22http:/www.example.com/cgi/success/?a=commitdiff_plain;h=d89cd9037ed46438e5c694782bc6230a9db61232;p=cargo.git Alternate registries. Allow users to add dependencies which are in alternate registries. The alternate registries are listed in the .cargo/config, and must there have a link to a conforming crate index. --- diff --git a/src/bin/login.rs b/src/bin/login.rs index c41118cc3..5e8e0e2db 100644 --- a/src/bin/login.rs +++ b/src/bin/login.rs @@ -51,7 +51,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult { let mut src = RegistrySource::remote(&src, config); src.update()?; let config = src.config()?.unwrap(); - let host = options.flag_host.clone().unwrap_or(config.api); + let host = options.flag_host.clone().unwrap_or(config.api.unwrap()); println!("please visit {}me and paste the API Token below", host); let mut line = String::new(); let input = io::stdin(); diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 4bfafc174..d4e91c010 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -31,7 +31,7 @@ //! })?; //! ``` //! -//! Notably you'll notice the `require` funciton called with your `Feature`, and +//! Notably you'll notice the `require` function called with your `Feature`, and //! then you use `chain_err` to tack on more context for why the feature was //! required when the feature isn't activated. //! @@ -122,6 +122,9 @@ features! { // A dummy feature that gates the usage of the `im-a-teapot` manifest // entry. This is basically just intended for tests. [unstable] test_dummy_unstable: bool, + + // Downloading packages from alternative registry indexes. + [unstable] alternative_registries: bool, } } diff --git a/src/cargo/core/source.rs b/src/cargo/core/source.rs deleted file mode 100644 index 01d659919..000000000 --- a/src/cargo/core/source.rs +++ /dev/null @@ -1,652 +0,0 @@ -use std::cmp::{self, Ordering}; -use std::collections::hash_map::{HashMap, Values, IterMut}; -use std::fmt::{self, Formatter}; -use std::hash::{self, Hash}; -use std::path::Path; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT}; -use std::sync::atomic::Ordering::SeqCst; - -use serde::ser; -use serde::de; -use url::Url; - -use core::{Package, PackageId, Registry}; -use ops; -use sources::git; -use sources::{PathSource, GitSource, RegistrySource, CRATES_IO}; -use sources::DirectorySource; -use util::{Config, CargoResult, ToUrl}; - -/// A Source finds and downloads remote packages based on names and -/// versions. -pub trait Source: Registry { - /// Returns the `SourceId` corresponding to this source - fn source_id(&self) -> &SourceId; - - /// The update method performs any network operations required to - /// get the entire list of all names, versions and dependencies of - /// packages managed by the Source. - fn update(&mut self) -> CargoResult<()>; - - /// The download method fetches the full package for each name and - /// version specified. - fn download(&mut self, package: &PackageId) -> CargoResult; - - /// Generates a unique string which represents the fingerprint of the - /// current state of the source. - /// - /// This fingerprint is used to determine the "fresheness" of the source - /// later on. It must be guaranteed that the fingerprint of a source is - /// constant if and only if the output product will remain constant. - /// - /// The `pkg` argument is the package which this fingerprint should only be - /// interested in for when this source may contain multiple packages. - fn fingerprint(&self, pkg: &Package) -> CargoResult; - - /// If this source supports it, verifies the source of the package - /// specified. - /// - /// Note that the source may also have performed other checksum-based - /// verification during the `download` step, but this is intended to be run - /// just before a crate is compiled so it may perform more expensive checks - /// which may not be cacheable. - fn verify(&self, _pkg: &PackageId) -> CargoResult<()> { - Ok(()) - } -} - -impl<'a, T: Source + ?Sized + 'a> Source for Box { - /// Forwards to `Source::source_id` - fn source_id(&self) -> &SourceId { - (**self).source_id() - } - - /// Forwards to `Source::update` - fn update(&mut self) -> CargoResult<()> { - (**self).update() - } - - /// Forwards to `Source::download` - fn download(&mut self, id: &PackageId) -> CargoResult { - (**self).download(id) - } - - /// Forwards to `Source::fingerprint` - fn fingerprint(&self, pkg: &Package) -> CargoResult { - (**self).fingerprint(pkg) - } - - /// Forwards to `Source::verify` - fn verify(&self, pkg: &PackageId) -> CargoResult<()> { - (**self).verify(pkg) - } -} - -/// The possible kinds of code source. Along with a URL, this fully defines the -/// source -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -enum Kind { - /// Kind::Git() represents a git repository - Git(GitReference), - /// represents a local path - Path, - /// represents the central registry - Registry, - /// represents a local filesystem-based registry - LocalRegistry, - /// represents a directory-based registry - Directory, -} - -/// Information to find a specific commit in a git repository -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum GitReference { - /// from a tag - Tag(String), - /// from the HEAD of a branch - Branch(String), - /// from a specific revision - Rev(String), -} - -/// Unique identifier for a source of packages. -#[derive(Clone, Eq, Debug)] -pub struct SourceId { - inner: Arc, -} - -/// Unique identifier for a source of packages. -#[derive(Eq, Clone, Debug)] -struct SourceIdInner { - /// The source URL - url: Url, - /// `git::canonicalize_url(url)` for the url field - canonical_url: Url, - /// The source kind - kind: Kind, - // e.g. the exact git revision of the specified branch for a Git Source - precise: Option, -} - -impl SourceId { - /// Create a SourceId object from the kind and url. - /// - /// The canonical url will be calculated, but the precise field will not - fn new(kind: Kind, url: Url) -> CargoResult { - let source_id = SourceId { - inner: Arc::new(SourceIdInner { - kind: kind, - canonical_url: git::canonicalize_url(&url)?, - url: url, - precise: None, - }), - }; - Ok(source_id) - } - - /// Parses a source URL and returns the corresponding ID. - /// - /// ## Example - /// - /// ``` - /// use cargo::core::SourceId; - /// SourceId::from_url("git+https://github.com/alexcrichton/\ - /// libssh2-static-sys#80e71a3021618eb05\ - /// 656c58fb7c5ef5f12bc747f"); - /// ``` - pub fn from_url(string: &str) -> CargoResult { - let mut parts = string.splitn(2, '+'); - let kind = parts.next().unwrap(); - let url = parts.next().ok_or_else(|| format!("invalid source `{}`", string))?; - - match kind { - "git" => { - let mut url = url.to_url()?; - let mut reference = GitReference::Branch("master".to_string()); - for (k, v) in url.query_pairs() { - match &k[..] { - // map older 'ref' to branch - "branch" | - "ref" => reference = GitReference::Branch(v.into_owned()), - - "rev" => reference = GitReference::Rev(v.into_owned()), - "tag" => reference = GitReference::Tag(v.into_owned()), - _ => {} - } - } - let precise = url.fragment().map(|s| s.to_owned()); - url.set_fragment(None); - url.set_query(None); - Ok(SourceId::for_git(&url, reference)?.with_precise(precise)) - }, - "registry" => { - let url = url.to_url()?; - Ok(SourceId::new(Kind::Registry, url)? - .with_precise(Some("locked".to_string()))) - } - "path" => { - let url = url.to_url()?; - SourceId::new(Kind::Path, url) - } - kind => Err(format!("unsupported source protocol: {}", kind).into()) - } - } - - /// A view of the `SourceId` that can be `Display`ed as a URL - pub fn to_url(&self) -> SourceIdToUrl { - SourceIdToUrl { inner: &*self.inner } - } - - /// Create a SourceId from a filesystem path. - /// - /// Pass absolute path - pub fn for_path(path: &Path) -> CargoResult { - let url = path.to_url()?; - SourceId::new(Kind::Path, url) - } - - /// Crate a SourceId from a git reference - pub fn for_git(url: &Url, reference: GitReference) -> CargoResult { - SourceId::new(Kind::Git(reference), url.clone()) - } - - /// Create a SourceId from a registry url - pub fn for_registry(url: &Url) -> CargoResult { - SourceId::new(Kind::Registry, url.clone()) - } - - /// Create a SourceId from a local registry path - pub fn for_local_registry(path: &Path) -> CargoResult { - let url = path.to_url()?; - SourceId::new(Kind::LocalRegistry, url) - } - - /// Create a SourceId from a directory path - pub fn for_directory(path: &Path) -> CargoResult { - let url = path.to_url()?; - SourceId::new(Kind::Directory, url) - } - - /// Returns the `SourceId` corresponding to the main repository. - /// - /// This is the main cargo registry by default, but it can be overridden in - /// a `.cargo/config`. - pub fn crates_io(config: &Config) -> CargoResult { - let cfg = ops::registry_configuration(config)?; - let url = if let Some(ref index) = cfg.index { - static WARNED: AtomicBool = ATOMIC_BOOL_INIT; - if !WARNED.swap(true, SeqCst) { - config.shell().warn("custom registry support via \ - the `registry.index` configuration is \ - being removed, this functionality \ - will not work in the future")?; - } - &index[..] - } else { - CRATES_IO - }; - let url = url.to_url()?; - SourceId::for_registry(&url) - } - - /// Get this source URL - pub fn url(&self) -> &Url { - &self.inner.url - } - - /// Is this source from a filesystem path - pub fn is_path(&self) -> bool { - self.inner.kind == Kind::Path - } - - /// Is this source from a registry (either local or not) - pub fn is_registry(&self) -> bool { - self.inner.kind == Kind::Registry || self.inner.kind == Kind::LocalRegistry - } - - /// Is this source from a git repository - pub fn is_git(&self) -> bool { - match self.inner.kind { - Kind::Git(_) => true, - _ => false, - } - } - - /// Creates an implementation of `Source` corresponding to this ID. - pub fn load<'a>(&self, config: &'a Config) -> CargoResult> { - trace!("loading SourceId; {}", self); - match self.inner.kind { - Kind::Git(..) => Ok(Box::new(GitSource::new(self, config)?)), - Kind::Path => { - let path = match self.inner.url.to_file_path() { - Ok(p) => p, - Err(()) => panic!("path sources cannot be remote"), - }; - Ok(Box::new(PathSource::new(&path, self, config))) - } - Kind::Registry => Ok(Box::new(RegistrySource::remote(self, config))), - Kind::LocalRegistry => { - let path = match self.inner.url.to_file_path() { - Ok(p) => p, - Err(()) => panic!("path sources cannot be remote"), - }; - Ok(Box::new(RegistrySource::local(self, &path, config))) - } - Kind::Directory => { - let path = match self.inner.url.to_file_path() { - Ok(p) => p, - Err(()) => panic!("path sources cannot be remote"), - }; - Ok(Box::new(DirectorySource::new(&path, self, config))) - } - } - } - - /// Get the value of the precise field - pub fn precise(&self) -> Option<&str> { - self.inner.precise.as_ref().map(|s| &s[..]) - } - - /// Get the git reference if this is a git source, otherwise None. - pub fn git_reference(&self) -> Option<&GitReference> { - match self.inner.kind { - Kind::Git(ref s) => Some(s), - _ => None, - } - } - - /// Create a new SourceId from this source with the given `precise` - pub fn with_precise(&self, v: Option) -> SourceId { - SourceId { - inner: Arc::new(SourceIdInner { - precise: v, - ..(*self.inner).clone() - }) - } - } - - /// Whether the remote registry is the standard https://crates.io - pub fn is_default_registry(&self) -> bool { - match self.inner.kind { - Kind::Registry => {} - _ => return false, - } - self.inner.url.to_string() == CRATES_IO - } - - /// Hash `self` - /// - /// For paths, remove the workspace prefix so the same source will give the - /// same hash in different locations. - pub fn stable_hash(&self, workspace: &Path, into: &mut S) { - if self.is_path() { - if let Ok(p) = self.inner.url.to_file_path().unwrap().strip_prefix(workspace) { - self.inner.kind.hash(into); - p.to_str().unwrap().hash(into); - return - } - } - self.hash(into) - } -} - -impl PartialEq for SourceId { - fn eq(&self, other: &SourceId) -> bool { - (*self.inner).eq(&*other.inner) - } -} - -impl PartialOrd for SourceId { - fn partial_cmp(&self, other: &SourceId) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for SourceId { - fn cmp(&self, other: &SourceId) -> Ordering { - self.inner.cmp(&other.inner) - } -} - -impl ser::Serialize for SourceId { - fn serialize(&self, s: S) -> Result - where S: ser::Serializer, - { - if self.is_path() { - None::.serialize(s) - } else { - s.collect_str(&self.to_url()) - } - } -} - -impl<'de> de::Deserialize<'de> for SourceId { - fn deserialize(d: D) -> Result - where D: de::Deserializer<'de>, - { - let string = String::deserialize(d)?; - SourceId::from_url(&string).map_err(de::Error::custom) - } -} - -impl fmt::Display for SourceId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match *self.inner { - SourceIdInner { kind: Kind::Path, ref url, .. } => { - fmt::Display::fmt(url, f) - } - SourceIdInner { kind: Kind::Git(ref reference), ref url, - ref precise, .. } => { - write!(f, "{}", url)?; - if let Some(pretty) = reference.pretty_ref() { - write!(f, "?{}", pretty)?; - } - - if let Some(ref s) = *precise { - let len = cmp::min(s.len(), 8); - write!(f, "#{}", &s[..len])?; - } - Ok(()) - } - SourceIdInner { kind: Kind::Registry, ref url, .. } | - SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => { - write!(f, "registry {}", url) - } - SourceIdInner { kind: Kind::Directory, ref url, .. } => { - write!(f, "dir {}", url) - } - } - } -} - -// This custom implementation handles situations such as when two git sources -// point at *almost* the same URL, but not quite, even when they actually point -// to the same repository. -/// This method tests for self and other values to be equal, and is used by ==. -/// -/// For git repositories, the canonical url is checked. -impl PartialEq for SourceIdInner { - fn eq(&self, other: &SourceIdInner) -> bool { - if self.kind != other.kind { - return false; - } - if self.url == other.url { - return true; - } - - match (&self.kind, &other.kind) { - (&Kind::Git(ref ref1), &Kind::Git(ref ref2)) => { - ref1 == ref2 && self.canonical_url == other.canonical_url - } - _ => false, - } - } -} - -impl PartialOrd for SourceIdInner { - fn partial_cmp(&self, other: &SourceIdInner) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for SourceIdInner { - fn cmp(&self, other: &SourceIdInner) -> Ordering { - match self.kind.cmp(&other.kind) { - Ordering::Equal => {} - ord => return ord, - } - match self.url.cmp(&other.url) { - Ordering::Equal => {} - ord => return ord, - } - match (&self.kind, &other.kind) { - (&Kind::Git(ref ref1), &Kind::Git(ref ref2)) => { - (ref1, &self.canonical_url).cmp(&(ref2, &other.canonical_url)) - } - _ => self.kind.cmp(&other.kind), - } - } -} - -// The hash of SourceId is used in the name of some Cargo folders, so shouldn't -// vary. `as_str` gives the serialisation of a url (which has a spec) and so -// insulates against possible changes in how the url crate does hashing. -impl Hash for SourceId { - fn hash(&self, into: &mut S) { - self.inner.kind.hash(into); - match *self.inner { - SourceIdInner { kind: Kind::Git(..), ref canonical_url, .. } => { - canonical_url.as_str().hash(into) - } - _ => self.inner.url.as_str().hash(into), - } - } -} - -/// A `Display`able view into a SourceId that will write it as a url -pub struct SourceIdToUrl<'a> { - inner: &'a SourceIdInner, -} - -impl<'a> fmt::Display for SourceIdToUrl<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self.inner { - SourceIdInner { kind: Kind::Path, ref url, .. } => { - write!(f, "path+{}", url) - } - SourceIdInner { - kind: Kind::Git(ref reference), ref url, ref precise, .. - } => { - write!(f, "git+{}", url)?; - if let Some(pretty) = reference.pretty_ref() { - write!(f, "?{}", pretty)?; - } - if let Some(precise) = precise.as_ref() { - write!(f, "#{}", precise)?; - } - Ok(()) - } - SourceIdInner { kind: Kind::Registry, ref url, .. } => { - write!(f, "registry+{}", url) - } - SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => { - write!(f, "local-registry+{}", url) - } - SourceIdInner { kind: Kind::Directory, ref url, .. } => { - write!(f, "directory+{}", url) - } - } - } -} - -impl GitReference { - /// Returns a `Display`able view of this git reference, or None if using - /// the head of the "master" branch - pub fn pretty_ref(&self) -> Option { - match *self { - GitReference::Branch(ref s) if *s == "master" => None, - _ => Some(PrettyRef { inner: self }), - } - } -} - -/// A git reference that can be `Display`ed -pub struct PrettyRef<'a> { - inner: &'a GitReference, -} - -impl<'a> fmt::Display for PrettyRef<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self.inner { - GitReference::Branch(ref b) => write!(f, "branch={}", b), - GitReference::Tag(ref s) => write!(f, "tag={}", s), - GitReference::Rev(ref s) => write!(f, "rev={}", s), - } - } -} - -/// A `HashMap` of `SourceId` -> `Box` -#[derive(Default)] -pub struct SourceMap<'src> { - map: HashMap>, -} - -/// A `std::collection::hash_map::Values` for `SourceMap` -pub type Sources<'a, 'src> = Values<'a, SourceId, Box>; - -/// A `std::collection::hash_map::IterMut` for `SourceMap` -pub struct SourcesMut<'a, 'src: 'a> { - inner: IterMut<'a, SourceId, Box>, -} - -impl<'src> SourceMap<'src> { - /// Create an empty map - pub fn new() -> SourceMap<'src> { - SourceMap { map: HashMap::new() } - } - - /// Like `HashMap::contains_key` - pub fn contains(&self, id: &SourceId) -> bool { - self.map.contains_key(id) - } - - /// Like `HashMap::get` - pub fn get(&self, id: &SourceId) -> Option<&(Source + 'src)> { - let source = self.map.get(id); - - source.map(|s| { - let s: &(Source + 'src) = &**s; - s - }) - } - - /// Like `HashMap::get_mut` - pub fn get_mut(&mut self, id: &SourceId) -> Option<&mut (Source + 'src)> { - self.map.get_mut(id).map(|s| { - let s: &mut (Source + 'src) = &mut **s; - s - }) - } - - /// Like `HashMap::get`, but first calculates the `SourceId` from a - /// `PackageId` - pub fn get_by_package_id(&self, pkg_id: &PackageId) -> Option<&(Source + 'src)> { - self.get(pkg_id.source_id()) - } - - /// Like `HashMap::insert`, but derives the SourceId key from the Source - pub fn insert(&mut self, source: Box) { - let id = source.source_id().clone(); - self.map.insert(id, source); - } - - /// Like `HashMap::is_empty` - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - /// Like `HashMap::len` - pub fn len(&self) -> usize { - self.map.len() - } - - /// Like `HashMap::values` - pub fn sources<'a>(&'a self) -> Sources<'a, 'src> { - self.map.values() - } - - /// Like `HashMap::iter_mut` - pub fn sources_mut<'a>(&'a mut self) -> SourcesMut<'a, 'src> { - SourcesMut { inner: self.map.iter_mut() } - } -} - -impl<'a, 'src> Iterator for SourcesMut<'a, 'src> { - type Item = (&'a SourceId, &'a mut (Source + 'src)); - fn next(&mut self) -> Option<(&'a SourceId, &'a mut (Source + 'src))> { - self.inner.next().map(|(a, b)| (a, &mut **b)) - } -} - -#[cfg(test)] -mod tests { - use super::{SourceId, Kind, GitReference}; - use util::ToUrl; - - #[test] - fn github_sources_equal() { - let loc = "https://github.com/foo/bar".to_url().unwrap(); - let master = Kind::Git(GitReference::Branch("master".to_string())); - let s1 = SourceId::new(master.clone(), loc).unwrap(); - - let loc = "git://github.com/foo/bar".to_url().unwrap(); - let s2 = SourceId::new(master, loc.clone()).unwrap(); - - assert_eq!(s1, s2); - - let foo = Kind::Git(GitReference::Branch("foo".to_string())); - let s3 = SourceId::new(foo, loc).unwrap(); - assert!(s1 != s3); - } -} diff --git a/src/cargo/core/source/mod.rs b/src/cargo/core/source/mod.rs new file mode 100644 index 000000000..bc54fa184 --- /dev/null +++ b/src/cargo/core/source/mod.rs @@ -0,0 +1,157 @@ +use std::collections::hash_map::{HashMap, Values, IterMut}; + +use core::{Package, PackageId, Registry}; +use util::CargoResult; + +mod source_id; + +pub use self::source_id::{SourceId, GitReference}; + +/// A Source finds and downloads remote packages based on names and +/// versions. +pub trait Source: Registry { + /// Returns the `SourceId` corresponding to this source + fn source_id(&self) -> &SourceId; + + /// The update method performs any network operations required to + /// get the entire list of all names, versions and dependencies of + /// packages managed by the Source. + fn update(&mut self) -> CargoResult<()>; + + /// The download method fetches the full package for each name and + /// version specified. + fn download(&mut self, package: &PackageId) -> CargoResult; + + /// Generates a unique string which represents the fingerprint of the + /// current state of the source. + /// + /// This fingerprint is used to determine the "fresheness" of the source + /// later on. It must be guaranteed that the fingerprint of a source is + /// constant if and only if the output product will remain constant. + /// + /// The `pkg` argument is the package which this fingerprint should only be + /// interested in for when this source may contain multiple packages. + fn fingerprint(&self, pkg: &Package) -> CargoResult; + + /// If this source supports it, verifies the source of the package + /// specified. + /// + /// Note that the source may also have performed other checksum-based + /// verification during the `download` step, but this is intended to be run + /// just before a crate is compiled so it may perform more expensive checks + /// which may not be cacheable. + fn verify(&self, _pkg: &PackageId) -> CargoResult<()> { + Ok(()) + } +} + +impl<'a, T: Source + ?Sized + 'a> Source for Box { + /// Forwards to `Source::source_id` + fn source_id(&self) -> &SourceId { + (**self).source_id() + } + + /// Forwards to `Source::update` + fn update(&mut self) -> CargoResult<()> { + (**self).update() + } + + /// Forwards to `Source::download` + fn download(&mut self, id: &PackageId) -> CargoResult { + (**self).download(id) + } + + /// Forwards to `Source::fingerprint` + fn fingerprint(&self, pkg: &Package) -> CargoResult { + (**self).fingerprint(pkg) + } + + /// Forwards to `Source::verify` + fn verify(&self, pkg: &PackageId) -> CargoResult<()> { + (**self).verify(pkg) + } +} + +/// A `HashMap` of `SourceId` -> `Box` +#[derive(Default)] +pub struct SourceMap<'src> { + map: HashMap>, +} + +/// A `std::collection::hash_map::Values` for `SourceMap` +pub type Sources<'a, 'src> = Values<'a, SourceId, Box>; + +/// A `std::collection::hash_map::IterMut` for `SourceMap` +pub struct SourcesMut<'a, 'src: 'a> { + inner: IterMut<'a, SourceId, Box>, +} + +impl<'src> SourceMap<'src> { + /// Create an empty map + pub fn new() -> SourceMap<'src> { + SourceMap { map: HashMap::new() } + } + + /// Like `HashMap::contains_key` + pub fn contains(&self, id: &SourceId) -> bool { + self.map.contains_key(id) + } + + /// Like `HashMap::get` + pub fn get(&self, id: &SourceId) -> Option<&(Source + 'src)> { + let source = self.map.get(id); + + source.map(|s| { + let s: &(Source + 'src) = &**s; + s + }) + } + + /// Like `HashMap::get_mut` + pub fn get_mut(&mut self, id: &SourceId) -> Option<&mut (Source + 'src)> { + self.map.get_mut(id).map(|s| { + let s: &mut (Source + 'src) = &mut **s; + s + }) + } + + /// Like `HashMap::get`, but first calculates the `SourceId` from a + /// `PackageId` + pub fn get_by_package_id(&self, pkg_id: &PackageId) -> Option<&(Source + 'src)> { + self.get(pkg_id.source_id()) + } + + /// Like `HashMap::insert`, but derives the SourceId key from the Source + pub fn insert(&mut self, source: Box) { + let id = source.source_id().clone(); + self.map.insert(id, source); + } + + /// Like `HashMap::is_empty` + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + /// Like `HashMap::len` + pub fn len(&self) -> usize { + self.map.len() + } + + /// Like `HashMap::values` + pub fn sources<'a>(&'a self) -> Sources<'a, 'src> { + self.map.values() + } + + /// Like `HashMap::iter_mut` + pub fn sources_mut<'a>(&'a mut self) -> SourcesMut<'a, 'src> { + SourcesMut { inner: self.map.iter_mut() } + } +} + +impl<'a, 'src> Iterator for SourcesMut<'a, 'src> { + type Item = (&'a SourceId, &'a mut (Source + 'src)); + fn next(&mut self) -> Option<(&'a SourceId, &'a mut (Source + 'src))> { + self.inner.next().map(|(a, b)| (a, &mut **b)) + } +} + diff --git a/src/cargo/core/source/source_id.rs b/src/cargo/core/source/source_id.rs new file mode 100644 index 000000000..1c37d3199 --- /dev/null +++ b/src/cargo/core/source/source_id.rs @@ -0,0 +1,537 @@ +use std::cmp::{self, Ordering}; +use std::fmt::{self, Formatter}; +use std::hash::{self, Hash}; +use std::path::Path; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT}; +use std::sync::atomic::Ordering::SeqCst; + +use serde::ser; +use serde::de; +use url::Url; + +use ops; +use sources::git; +use sources::{PathSource, GitSource, RegistrySource, CRATES_IO}; +use sources::DirectorySource; +use util::{Config, ConfigValue as CV, CargoResult, ToUrl}; + +/// Unique identifier for a source of packages. +#[derive(Clone, Eq, Debug)] +pub struct SourceId { + inner: Arc, +} + +#[derive(Eq, Clone, Debug)] +struct SourceIdInner { + /// The source URL + url: Url, + /// `git::canonicalize_url(url)` for the url field + canonical_url: Url, + /// The source kind + kind: Kind, + // e.g. the exact git revision of the specified branch for a Git Source + precise: Option, +} + +/// The possible kinds of code source. Along with a URL, this fully defines the +/// source +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum Kind { + /// Kind::Git() represents a git repository + Git(GitReference), + /// represents a local path + Path, + /// represents a remote registry + Registry, + /// represents a local filesystem-based registry + LocalRegistry, + /// represents a directory-based registry + Directory, +} + +/// Information to find a specific commit in a git repository +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum GitReference { + /// from a tag + Tag(String), + /// from the HEAD of a branch + Branch(String), + /// from a specific revision + Rev(String), +} + +impl SourceId { + /// Create a SourceId object from the kind and url. + /// + /// The canonical url will be calculated, but the precise field will not + fn new(kind: Kind, url: Url) -> CargoResult { + let source_id = SourceId { + inner: Arc::new(SourceIdInner { + kind: kind, + canonical_url: git::canonicalize_url(&url)?, + url: url, + precise: None, + }), + }; + Ok(source_id) + } + + /// Parses a source URL and returns the corresponding ID. + /// + /// ## Example + /// + /// ``` + /// use cargo::core::SourceId; + /// SourceId::from_url("git+https://github.com/alexcrichton/\ + /// libssh2-static-sys#80e71a3021618eb05\ + /// 656c58fb7c5ef5f12bc747f"); + /// ``` + pub fn from_url(string: &str) -> CargoResult { + let mut parts = string.splitn(2, '+'); + let kind = parts.next().unwrap(); + let url = parts.next().ok_or_else(|| format!("invalid source `{}`", string))?; + + match kind { + "git" => { + let mut url = url.to_url()?; + let mut reference = GitReference::Branch("master".to_string()); + for (k, v) in url.query_pairs() { + match &k[..] { + // map older 'ref' to branch + "branch" | + "ref" => reference = GitReference::Branch(v.into_owned()), + + "rev" => reference = GitReference::Rev(v.into_owned()), + "tag" => reference = GitReference::Tag(v.into_owned()), + _ => {} + } + } + let precise = url.fragment().map(|s| s.to_owned()); + url.set_fragment(None); + url.set_query(None); + Ok(SourceId::for_git(&url, reference)?.with_precise(precise)) + }, + "registry" => { + let url = url.to_url()?; + Ok(SourceId::new(Kind::Registry, url)? + .with_precise(Some("locked".to_string()))) + } + "path" => { + let url = url.to_url()?; + SourceId::new(Kind::Path, url) + } + kind => Err(format!("unsupported source protocol: {}", kind).into()) + } + } + + /// A view of the `SourceId` that can be `Display`ed as a URL + pub fn to_url(&self) -> SourceIdToUrl { + SourceIdToUrl { inner: &*self.inner } + } + + /// Create a SourceId from a filesystem path. + /// + /// Pass absolute path + pub fn for_path(path: &Path) -> CargoResult { + let url = path.to_url()?; + SourceId::new(Kind::Path, url) + } + + /// Crate a SourceId from a git reference + pub fn for_git(url: &Url, reference: GitReference) -> CargoResult { + SourceId::new(Kind::Git(reference), url.clone()) + } + + /// Create a SourceId from a registry url + pub fn for_registry(url: &Url) -> CargoResult { + SourceId::new(Kind::Registry, url.clone()) + } + + /// Create a SourceId from a local registry path + pub fn for_local_registry(path: &Path) -> CargoResult { + let url = path.to_url()?; + SourceId::new(Kind::LocalRegistry, url) + } + + /// Create a SourceId from a directory path + pub fn for_directory(path: &Path) -> CargoResult { + let url = path.to_url()?; + SourceId::new(Kind::Directory, url) + } + + /// Returns the `SourceId` corresponding to the main repository. + /// + /// This is the main cargo registry by default, but it can be overridden in + /// a `.cargo/config`. + pub fn crates_io(config: &Config) -> CargoResult { + let cfg = ops::registry_configuration(config)?; + let url = if let Some(ref index) = cfg.index { + static WARNED: AtomicBool = ATOMIC_BOOL_INIT; + if !WARNED.swap(true, SeqCst) { + config.shell().warn("custom registry support via \ + the `registry.index` configuration is \ + being removed, this functionality \ + will not work in the future")?; + } + &index[..] + } else { + CRATES_IO + }; + let url = url.to_url()?; + SourceId::for_registry(&url) + } + + pub fn alt_registry(config: &Config, key: &str) -> CargoResult { + let registries = config.get_table("registries")?; + match registries.as_ref().and_then(|registries| registries.val.get(key)) { + Some(registry) => { + let index = match *registry { + CV::Table(ref registry, _) => { + match registry.get("index") { + Some(index) => index.string(&format!("registries.{}", key))?.0, + None => return Err(format!("No index for registry `{}`", key).into()), + } + } + _ => registry.expected("table", &format!("registries.{}", key))? + }; + + let url = index.to_url()?; + + Ok(SourceId { + inner: Arc::new(SourceIdInner { + kind: Kind::Registry, + canonical_url: git::canonicalize_url(&url)?, + url: url, + precise: None, + }), + }) + } + None => Err(format!("Required unknown registry source: `{}`", key).into()) + } + } + + /// Get this source URL + pub fn url(&self) -> &Url { + &self.inner.url + } + + pub fn display_registry(&self) -> String { + format!("registry `{}`", self.url()) + } + + /// Is this source from a filesystem path + pub fn is_path(&self) -> bool { + self.inner.kind == Kind::Path + } + + /// Is this source from a registry (either local or not) + pub fn is_registry(&self) -> bool { + match self.inner.kind { + Kind::Registry | Kind::LocalRegistry => true, + _ => false, + } + } + + /// Is this source from a git repository + pub fn is_git(&self) -> bool { + match self.inner.kind { + Kind::Git(_) => true, + _ => false, + } + } + + /// Creates an implementation of `Source` corresponding to this ID. + pub fn load<'a>(&self, config: &'a Config) -> CargoResult> { + trace!("loading SourceId; {}", self); + match self.inner.kind { + Kind::Git(..) => Ok(Box::new(GitSource::new(self, config)?)), + Kind::Path => { + let path = match self.inner.url.to_file_path() { + Ok(p) => p, + Err(()) => panic!("path sources cannot be remote"), + }; + Ok(Box::new(PathSource::new(&path, self, config))) + } + Kind::Registry => Ok(Box::new(RegistrySource::remote(self, config))), + Kind::LocalRegistry => { + let path = match self.inner.url.to_file_path() { + Ok(p) => p, + Err(()) => panic!("path sources cannot be remote"), + }; + Ok(Box::new(RegistrySource::local(self, &path, config))) + } + Kind::Directory => { + let path = match self.inner.url.to_file_path() { + Ok(p) => p, + Err(()) => panic!("path sources cannot be remote"), + }; + Ok(Box::new(DirectorySource::new(&path, self, config))) + } + } + } + + /// Get the value of the precise field + pub fn precise(&self) -> Option<&str> { + self.inner.precise.as_ref().map(|s| &s[..]) + } + + /// Get the git reference if this is a git source, otherwise None. + pub fn git_reference(&self) -> Option<&GitReference> { + match self.inner.kind { + Kind::Git(ref s) => Some(s), + _ => None, + } + } + + /// Create a new SourceId from this source with the given `precise` + pub fn with_precise(&self, v: Option) -> SourceId { + SourceId { + inner: Arc::new(SourceIdInner { + precise: v, + ..(*self.inner).clone() + }) + } + } + + /// Whether the remote registry is the standard https://crates.io + pub fn is_default_registry(&self) -> bool { + match self.inner.kind { + Kind::Registry => {} + _ => return false, + } + self.inner.url.to_string() == CRATES_IO + } + + /// Hash `self` + /// + /// For paths, remove the workspace prefix so the same source will give the + /// same hash in different locations. + pub fn stable_hash(&self, workspace: &Path, into: &mut S) { + if self.is_path() { + if let Ok(p) = self.inner.url.to_file_path().unwrap().strip_prefix(workspace) { + self.inner.kind.hash(into); + p.to_str().unwrap().hash(into); + return + } + } + self.hash(into) + } +} + +impl PartialEq for SourceId { + fn eq(&self, other: &SourceId) -> bool { + (*self.inner).eq(&*other.inner) + } +} + +impl PartialOrd for SourceId { + fn partial_cmp(&self, other: &SourceId) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for SourceId { + fn cmp(&self, other: &SourceId) -> Ordering { + self.inner.cmp(&other.inner) + } +} + +impl ser::Serialize for SourceId { + fn serialize(&self, s: S) -> Result + where S: ser::Serializer, + { + if self.is_path() { + None::.serialize(s) + } else { + s.collect_str(&self.to_url()) + } + } +} + +impl<'de> de::Deserialize<'de> for SourceId { + fn deserialize(d: D) -> Result + where D: de::Deserializer<'de>, + { + let string = String::deserialize(d)?; + SourceId::from_url(&string).map_err(de::Error::custom) + } +} + +impl fmt::Display for SourceId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match *self.inner { + SourceIdInner { kind: Kind::Path, ref url, .. } => { + fmt::Display::fmt(url, f) + } + SourceIdInner { kind: Kind::Git(ref reference), ref url, + ref precise, .. } => { + write!(f, "{}", url)?; + if let Some(pretty) = reference.pretty_ref() { + write!(f, "?{}", pretty)?; + } + + if let Some(ref s) = *precise { + let len = cmp::min(s.len(), 8); + write!(f, "#{}", &s[..len])?; + } + Ok(()) + } + SourceIdInner { kind: Kind::Registry, ref url, .. } | + SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => { + write!(f, "registry `{}`", url) + } + SourceIdInner { kind: Kind::Directory, ref url, .. } => { + write!(f, "dir {}", url) + } + } + } +} + +// This custom implementation handles situations such as when two git sources +// point at *almost* the same URL, but not quite, even when they actually point +// to the same repository. +/// This method tests for self and other values to be equal, and is used by ==. +/// +/// For git repositories, the canonical url is checked. +impl PartialEq for SourceIdInner { + fn eq(&self, other: &SourceIdInner) -> bool { + if self.kind != other.kind { + return false; + } + if self.url == other.url { + return true; + } + + match (&self.kind, &other.kind) { + (&Kind::Git(ref ref1), &Kind::Git(ref ref2)) => { + ref1 == ref2 && self.canonical_url == other.canonical_url + } + _ => false, + } + } +} + +impl PartialOrd for SourceIdInner { + fn partial_cmp(&self, other: &SourceIdInner) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for SourceIdInner { + fn cmp(&self, other: &SourceIdInner) -> Ordering { + match self.kind.cmp(&other.kind) { + Ordering::Equal => {} + ord => return ord, + } + match self.url.cmp(&other.url) { + Ordering::Equal => {} + ord => return ord, + } + match (&self.kind, &other.kind) { + (&Kind::Git(ref ref1), &Kind::Git(ref ref2)) => { + (ref1, &self.canonical_url).cmp(&(ref2, &other.canonical_url)) + } + _ => self.kind.cmp(&other.kind), + } + } +} + +// The hash of SourceId is used in the name of some Cargo folders, so shouldn't +// vary. `as_str` gives the serialisation of a url (which has a spec) and so +// insulates against possible changes in how the url crate does hashing. +impl Hash for SourceId { + fn hash(&self, into: &mut S) { + self.inner.kind.hash(into); + match *self.inner { + SourceIdInner { kind: Kind::Git(..), ref canonical_url, .. } => { + canonical_url.as_str().hash(into) + } + _ => self.inner.url.as_str().hash(into), + } + } +} + +/// A `Display`able view into a SourceId that will write it as a url +pub struct SourceIdToUrl<'a> { + inner: &'a SourceIdInner, +} + +impl<'a> fmt::Display for SourceIdToUrl<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self.inner { + SourceIdInner { kind: Kind::Path, ref url, .. } => { + write!(f, "path+{}", url) + } + SourceIdInner { + kind: Kind::Git(ref reference), ref url, ref precise, .. + } => { + write!(f, "git+{}", url)?; + if let Some(pretty) = reference.pretty_ref() { + write!(f, "?{}", pretty)?; + } + if let Some(precise) = precise.as_ref() { + write!(f, "#{}", precise)?; + } + Ok(()) + } + SourceIdInner { kind: Kind::Registry, ref url, .. } => { + write!(f, "registry+{}", url) + } + SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => { + write!(f, "local-registry+{}", url) + } + SourceIdInner { kind: Kind::Directory, ref url, .. } => { + write!(f, "directory+{}", url) + } + } + } +} + +impl GitReference { + /// Returns a `Display`able view of this git reference, or None if using + /// the head of the "master" branch + pub fn pretty_ref(&self) -> Option { + match *self { + GitReference::Branch(ref s) if *s == "master" => None, + _ => Some(PrettyRef { inner: self }), + } + } +} + +/// A git reference that can be `Display`ed +pub struct PrettyRef<'a> { + inner: &'a GitReference, +} + +impl<'a> fmt::Display for PrettyRef<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self.inner { + GitReference::Branch(ref b) => write!(f, "branch={}", b), + GitReference::Tag(ref s) => write!(f, "tag={}", s), + GitReference::Rev(ref s) => write!(f, "rev={}", s), + } + } +} + +#[cfg(test)] +mod tests { + use super::{SourceId, Kind, GitReference}; + use util::ToUrl; + + #[test] + fn github_sources_equal() { + let loc = "https://github.com/foo/bar".to_url().unwrap(); + let master = Kind::Git(GitReference::Branch("master".to_string())); + let s1 = SourceId::new(master.clone(), loc).unwrap(); + + let loc = "git://github.com/foo/bar".to_url().unwrap(); + let s2 = SourceId::new(master, loc.clone()).unwrap(); + + assert_eq!(s1, s2); + + let foo = Kind::Git(GitReference::Branch("foo".to_string())); + let s3 = SourceId::new(foo, loc).unwrap(); + assert!(s1 != s3); + } +} diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 42ff2f872..4d21be922 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -83,11 +83,18 @@ fn verify_dependencies(pkg: &Package, registry_src: &SourceId) a version", dep.name()) } } else if dep.source_id() != registry_src { - bail!("crates cannot be published to crates.io with dependencies sourced from \ - a repository\neither publish `{}` as its own crate on crates.io and \ - specify a crates.io version as a dependency or pull it into this \ - repository and specify it with a path and version\n(crate `{}` has \ - repository path `{}`)", dep.name(), dep.name(), dep.source_id()); + if dep.source_id().is_registry() { + bail!("crates cannot be published to crates.io with dependencies sourced from other\n\ + registries either publish `{}` on crates.io or pull it into this repository\n\ + and specify it with a path and version\n\ + (crate `{}` is pulled from {}", dep.name(), dep.name(), dep.source_id()); + } else { + bail!("crates cannot be published to crates.io with dependencies sourced from \ + a repository\neither publish `{}` as its own crate on crates.io and \ + specify a crates.io version as a dependency or pull it into this \ + repository and specify it with a path and version\n(crate `{}` has \ + repository path `{}`)", dep.name(), dep.name(), dep.source_id()); + } } } Ok(()) @@ -205,7 +212,7 @@ pub fn registry(config: &Config, src.update().chain_err(|| { format!("failed to update {}", sid) })?; - (src.config()?).unwrap().api + (src.config()?).unwrap().api.unwrap() }; let handle = http_handle(config)?; Ok((Registry::new_handle(api_host, token, handle), sid)) diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index c967e2ebc..8b907f7d3 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -198,7 +198,7 @@ pub struct RegistryConfig { /// API endpoint for the registry. This is what's actually hit to perform /// operations like yanks, owner modifications, publish new crates, etc. - pub api: String, + pub api: Option, } #[derive(Deserialize)] diff --git a/src/cargo/sources/registry/remote.rs b/src/cargo/sources/registry/remote.rs index 6704282b8..846226107 100644 --- a/src/cargo/sources/registry/remote.rs +++ b/src/cargo/sources/registry/remote.rs @@ -166,8 +166,7 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { let _lock = self.index_path.open_rw(Path::new(INDEX_LOCK), self.config, "the registry index")?; - self.config.shell().status("Updating", - format!("registry `{}`", self.source_id.url()))?; + self.config.shell().status("Updating", self.source_id.display_registry())?; // git fetch origin master let url = self.source_id.url(); diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index 38e2e303f..252d8cb01 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -762,7 +762,7 @@ impl ConfigValue { } } - fn expected(&self, wanted: &str, key: &str) -> CargoResult { + pub fn expected(&self, wanted: &str, key: &str) -> CargoResult { Err(format!("expected a {}, but found a {} for `{}` in {}", wanted, self.desc(), key, self.definition_path().display()).into()) diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index c2220ac2d..487d46f93 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -14,7 +14,7 @@ use url::Url; use core::{SourceId, Profiles, PackageIdSpec, GitReference, WorkspaceConfig}; use core::{Summary, Manifest, Target, Dependency, PackageId}; -use core::{EitherManifest, VirtualManifest, Features}; +use core::{EitherManifest, VirtualManifest, Features, Feature}; use core::dependency::{Kind, Platform}; use core::manifest::{LibKind, Profile, ManifestMetadata}; use sources::CRATES_IO; @@ -184,6 +184,7 @@ impl<'de> de::Deserialize<'de> for TomlDependency { #[derive(Deserialize, Serialize, Clone, Debug, Default)] pub struct DetailedTomlDependency { version: Option, + registry: Option, path: Option, git: Option, branch: Option, @@ -429,6 +430,7 @@ struct Context<'a, 'b> { warnings: &'a mut Vec, platform: Option, root: &'a Path, + features: &'a Features, } impl TomlManifest { @@ -511,6 +513,11 @@ impl TomlManifest { let mut warnings = vec![]; let mut errors = vec![]; + // Parse features first so they will be available when parsing other parts of the toml + let empty = Vec::new(); + let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty); + let features = Features::new(&cargo_features, &mut warnings)?; + let project = me.project.as_ref().or_else(|| me.package.as_ref()); let project = project.ok_or_else(|| { CargoError::from("no `package` or `project` section found.") @@ -551,6 +558,7 @@ impl TomlManifest { nested_paths: &mut nested_paths, config: config, warnings: &mut warnings, + features: &features, platform: None, root: package_root, }; @@ -649,9 +657,6 @@ impl TomlManifest { }; let profiles = build_profiles(&me.profile); let publish = project.publish.unwrap_or(true); - let empty = Vec::new(); - let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty); - let features = Features::new(cargo_features, &mut warnings)?; let mut manifest = Manifest::new(summary, targets, exclude, @@ -721,6 +726,7 @@ impl TomlManifest { config: config, warnings: &mut warnings, platform: None, + features: &Features::default(), // @alex: is this right? root: root }; (me.replace(&mut cx)?, me.patch(&mut cx)?) @@ -867,8 +873,12 @@ impl TomlDependency { } } - let new_source_id = match (details.git.as_ref(), details.path.as_ref()) { - (Some(git), maybe_path) => { + let new_source_id = match (details.git.as_ref(), details.path.as_ref(), details.registry.as_ref()) { + (Some(_), _, Some(_)) => bail!("dependency ({}) specification is ambiguous. \ + Only one of `git` or `registry` is allowed.", name), + (_, Some(_), Some(_)) => bail!("dependency ({}) specification is ambiguous. \ + Only one of `path` or `registry` is allowed.", name), + (Some(git), maybe_path, _) => { if maybe_path.is_some() { let msg = format!("dependency ({}) specification is ambiguous. \ Only one of `git` or `path` is allowed. \ @@ -895,7 +905,7 @@ impl TomlDependency { let loc = git.to_url()?; SourceId::for_git(&loc, reference)? }, - (None, Some(path)) => { + (None, Some(path), _) => { cx.nested_paths.push(PathBuf::from(path)); // If the source id for the package we're parsing is a path // source, then we normalize the path here to get rid of @@ -913,7 +923,11 @@ impl TomlDependency { cx.source_id.clone() } }, - (None, None) => SourceId::crates_io(cx.config)?, + (None, None, Some(registry)) => { + cx.features.require(Feature::alternative_registries())?; + SourceId::alt_registry(cx.config, registry)? + } + (None, None, None) => SourceId::crates_io(cx.config)?, }; let version = details.version.as_ref().map(|v| &v[..]); diff --git a/tests/alt-registry.rs b/tests/alt-registry.rs new file mode 100644 index 000000000..9d701ba6b --- /dev/null +++ b/tests/alt-registry.rs @@ -0,0 +1,145 @@ +extern crate cargotest; +extern crate hamcrest; + +use cargotest::ChannelChanger; +use cargotest::support::registry::{self, Package}; +use cargotest::support::{project, execs}; +use hamcrest::assert_that; + +#[test] +fn is_feature_gated() { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + version = "0.0.1" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + Package::new("bar", "0.0.1").alternative(true).publish(); + + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101) + .with_stderr_contains(" feature `alternative-registries` is required")); +} + +#[test] +fn depend_on_alt_registry() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["alternative-registries"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + version = "0.0.1" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + Package::new("bar", "0.0.1").alternative(true).publish(); + + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stderr(&format!("\ +[UPDATING] registry `{reg}` +[DOWNLOADING] bar v0.0.1 (registry `file://[..]`) +[COMPILING] bar v0.0.1 (registry `file://[..]`) +[COMPILING] foo v0.0.1 ({dir}) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs +", + dir = p.url(), + reg = registry::alt_registry()))); + + assert_that(p.cargo("clean").masquerade_as_nightly_cargo(), execs().with_status(0)); + + // Don't download a second time + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stderr(&format!("\ +[COMPILING] bar v0.0.1 (registry `file://[..]`) +[COMPILING] foo v0.0.1 ({dir}) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs +", + dir = p.url()))); +} + +#[test] +fn registry_incompatible_with_path() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["alternative-registries"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101) + .with_stderr_contains(" dependency (bar) specification is ambiguous. Only one of `path` or `registry` is allowed.")); +} + +#[test] +fn registry_incompatible_with_git() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["alternative-registries"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + git = "" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101) + .with_stderr_contains(" dependency (bar) specification is ambiguous. Only one of `git` or `registry` is allowed.")); +} + + +#[test] +fn cannot_publish_with_registry_dependency() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["alternative-registries"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + version = "0.0.1" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + Package::new("bar", "0.0.1").alternative(true).publish(); + + assert_that(p.cargo("publish").masquerade_as_nightly_cargo() + .arg("--index").arg(registry::alt_registry().to_string()), + execs().with_status(101)); +} diff --git a/tests/bad-config.rs b/tests/bad-config.rs index cff576dbb..30746f0c9 100644 --- a/tests/bad-config.rs +++ b/tests/bad-config.rs @@ -794,7 +794,7 @@ fn bad_source_config2() { error: failed to load source for a dependency on `bar` Caused by: - Unable to update registry https://[..] + Unable to update registry `https://[..]` Caused by: could not find a configured source with the name `bar` \ @@ -826,7 +826,7 @@ fn bad_source_config3() { error: failed to load source for a dependency on `bar` Caused by: - Unable to update registry https://[..] + Unable to update registry `https://[..]` Caused by: detected a cycle of `replace-with` sources, [..] @@ -861,7 +861,7 @@ fn bad_source_config4() { error: failed to load source for a dependency on `bar` Caused by: - Unable to update registry https://[..] + Unable to update registry `https://[..]` Caused by: detected a cycle of `replace-with` sources, the source `crates-io` is \ diff --git a/tests/cargotest/support/registry.rs b/tests/cargotest/support/registry.rs index 3046d2e20..1e0b28cd0 100644 --- a/tests/cargotest/support/registry.rs +++ b/tests/cargotest/support/registry.rs @@ -18,6 +18,8 @@ pub fn registry_path() -> PathBuf { paths::root().join("registry") } pub fn registry() -> Url { Url::from_file_path(&*registry_path()).ok().unwrap() } pub fn dl_path() -> PathBuf { paths::root().join("dl") } pub fn dl_url() -> Url { Url::from_file_path(&*dl_path()).ok().unwrap() } +pub fn alt_registry_path() -> PathBuf { paths::root().join("alternative-registry") } +pub fn alt_registry() -> Url { Url::from_file_path(&*alt_registry_path()).ok().unwrap() } pub struct Package { name: String, @@ -28,6 +30,7 @@ pub struct Package { yanked: bool, features: HashMap>, local: bool, + alternative: bool, } struct Dependency { @@ -54,7 +57,10 @@ pub fn init() { [source.dummy-registry] registry = '{reg}' - "#, reg = registry()).as_bytes())); + + [registries.alternative] + index = '{alt}' + "#, reg = registry(), alt = alt_registry()).as_bytes())); // Init a new registry repo(®istry_path()) @@ -63,6 +69,14 @@ pub fn init() { "#, dl_url())) .build(); fs::create_dir_all(dl_path().join("api/v1/crates")).unwrap(); + + // Init an alt registry + repo(&alt_registry_path()) + .file("config.json", &format!(r#" + {{"dl":"{0}","api":"{0}"}} + "#, dl_url())) + .build(); + fs::create_dir_all(dl_path().join("api/v1/crates")).unwrap(); } impl Package { @@ -77,6 +91,7 @@ impl Package { yanked: false, features: HashMap::new(), local: false, + alternative: false, } } @@ -85,6 +100,11 @@ impl Package { self } + pub fn alternative(&mut self, alternative: bool) -> &mut Package { + self.alternative = alternative; + self + } + pub fn file(&mut self, name: &str, contents: &str) -> &mut Package { self.files.push((name.to_string(), contents.to_string())); self @@ -174,11 +194,13 @@ impl Package { _ => format!("{}/{}/{}", &self.name[0..2], &self.name[2..4], self.name), }; + let registry_path = if self.alternative { alt_registry_path() } else { registry_path() }; + // Write file/line in the index let dst = if self.local { - registry_path().join("index").join(&file) + registry_path.join("index").join(&file) } else { - registry_path().join(&file) + registry_path.join(&file) }; let mut prev = String::new(); let _ = File::open(&dst).and_then(|mut f| f.read_to_string(&mut prev)); @@ -188,7 +210,7 @@ impl Package { // Add the new file to the index if !self.local { - let repo = t!(git2::Repository::open(®istry_path())); + let repo = t!(git2::Repository::open(®istry_path)); let mut index = t!(repo.index()); t!(index.add_path(Path::new(&file))); t!(index.write());